﻿using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Linq;

namespace LightCAD.Core.Elements
{
    public class LcGroup : LcElement, IElementSet
    {
        private bool isSelected;
        public string Name { get; set; }
        public string Description { get; set; }

        public override bool IsSelected
        {
            get
            {
                return isSelected;
            }
            set {
                this.isSelected = value;
                foreach(var ele in this.Elements)
                {
                    ele.IsSelected = value;
                }
            }
        }

        [JsonInclude]
        public ElementCollection Elements { get; } = new ElementCollection();
        public bool IsFireChangedEvent { get; set; } = true;
        public LcGroup()
        {
            this.Type = BuiltinElementType.Group;
        }

        public void InsertElement(LcElement element)
        {
            element.Parent = this;
            this.ResetBoundingBox();
            if (!IsFireChangedEvent)
            {
                this.Elements.Add(element);
            }
            else
            {
                var args = new ObjectChangedEventArgs
                {
                    Type = ObjectChangeType.Insert,
                    Target = element
                };
                element.OnObjectChangedBefore(args);
                if (args.IsCancel) return;

                this.Document.OnObjectChangedBefore(args);
                if (args.IsCancel) return;

                element.Document = this.Document;
                this.Elements.Add(element);

                element.OnObjectChangedAfter(args);
                this.Document.OnObjectChangedAfter(args);
            }

        }
        public bool RemoveElement(LcElement element)
        {
            if (element == null || this != element.Parent)
            {
                throw new ArgumentException(SR.ElementNotInContainer, nameof(element));
            }
            

            if (!IsFireChangedEvent)
            {
               var removed= this.Elements.Remove(element);
                if (removed)
                {
                    this.ResetBoundingBox();
                }
                return removed;
            }
            else
            {
                if (!this.Elements.Contains(element))
                {
                    return false;
                }

                var args = new ObjectChangedEventArgs
                {
                    Type = ObjectChangeType.Remove,
                    Target = element
                };
                element.OnObjectChangedBefore(args);
                if (args.IsCancel) return false;

                this.Document.OnObjectChangedBefore(args);
                if (args.IsCancel) return false;

                this.Elements.Remove(element);
                element.Parent = null;

                element.OnObjectChangedAfter(args);
                this.Document.OnObjectChangedAfter(args);

                this.ResetBoundingBox();
                return true;
            }

        }
        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var grp = src as LcGroup;
            grp.Name = this.Name;
            grp.Description = this.Description;

        }
        public override LcElement Clone()
        {
            var clone = this.Document.CreateObject<LcGroup>();
            clone.Copy(this);
            foreach (var ele in this.Elements)
            {
                var cloneEle = ele.Clone();
                clone.Elements.Add(cloneEle);
            }
            return clone;
        }

        public override Box2d GetBoundingBox()
        {
            var box = Box2d.Empty;
            foreach (var ele in this.Elements)
            {
                box.Union(ele.GetBoundingBox());
            }
            return box;
        }
        public override  Box2d GetBoundingBox(Matrix3d matrix)
        {
            var box = Box2d.Empty;
            foreach (var ele in this.Elements)
            {
                box.Union(ele.GetBoundingBox(matrix));
            }
            return box;
        }
        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            foreach (var ele in this.Elements)
            {
                if (ele.IntersectWithBox(testPoly)) return true;
            }
            return false;
        }

        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            var thisBox = this.BoundingBox;
            if (!thisBox.IntersectWith(testPoly.BoundingBox))
            {
                return false;
            }
            foreach (var ele in this.Elements)
            {
                if (!ele.IncludedByBox(testPoly)) return false;
            }
            return true;
        }

        public override void Translate(double dx, double dy)
        {
            foreach (var ele in this.Elements)
            {
                ele.Translate(dx, dy);
            }
            ResetBoundingBox();
        }

        public override void Scale( Vector2d basePoint, double scaleFactor)
        {
            foreach(var subEle in this.Elements)
            {
                subEle.Scale(basePoint,scaleFactor);
            }
            ResetBoundingBox();
        }
        public override void Rotate(Vector2d basePoint, double rotateAngle)
        {
            foreach (var subEle in this.Elements)
            {
                subEle.Rotate(basePoint, rotateAngle);
            }
            ResetBoundingBox();
        }
        public override void Mirror(Vector2d axisStart, Vector2d axisEnd)
        {
            foreach (var subEle in this.Elements)
            {
                subEle.Mirror(axisStart, axisEnd);
            }
            ResetBoundingBox();
        }

        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            if(!string.IsNullOrEmpty(this.Name))
                writer.WriteStringProperty(nameof(Name), this.Name);

            if(!string.IsNullOrEmpty(this.Description))
                writer.WriteStringProperty(nameof(Description), this.Description);

            writer.WritePropertyName(nameof(Elements));
            writer.WriteStartArray();
            foreach (var subEle in this.Elements)
            {
                writer.WriteElementObject(subEle, soptions);
            }
            writer.WriteEndArray();
        }
        public override void ReadProperties(ref JsonElement jele)
        {
            if(jele.TryGetProperty(nameof(this.Name), out JsonElement nameProp))
                this.Name = nameProp.GetString();

            if (jele.TryGetProperty(nameof(this.Description), out JsonElement descrProp))
                this.Description = descrProp.GetString();

            var arr=jele.GetProperty(nameof(Elements)).EnumerateArray().ToArray();
            for(var i=0;i<arr.Length;i++)
            {
                var ele = arr[i].ReadElementObject(this.Document);
                this.Elements.Add(ele);
            }
        }
    }

    public class GroupCollection : KeyedCollection<long, LcGroup>
    {
        protected override long GetKeyForItem(LcGroup item)
        {
            return item.Id;
        }
    }
}